Agency
Alternative name: Board, State Board
As we implied in our last section, the way Users obtain Earnings is according to collections of rules. We didn't invent them - they are modeled after real-life constraints. In the real life of (virtual or physical) pen, paper, stamps and signatures, a state-level Agency/Board defines rules according to which the interested parties - various type of professionals - bring proof of continuous education and get to keep their license(s).
This means that an Agency is a conglomerate of rules that have to be kept in a state of internal cohesion. From a modeling perspective, the real-life existence of State-level Agencies/Boards is an immense "free lunch" for us, architecturally; it is a lynchpin for our system. It's like when trying to fit a number of objects of various size in a box - the largest one(s) will dictate how the others fit alongside it.
Here's how the Agency is modeled in Tangible Cred (TCred):
class Agency extends Aggregate {
# Scalar values
private string $organisation_code;
private string $display_name;
private string $website;
/**
* @var string Organization help text from Figma
*/
private string $description;
private TimedFrontendNotice $frontend_notice;
/**
* @var array<int, string>
*/
private array $profession_codes;
/**
* @var int[]
*/
private array $point_types;
/**
* @var int[]
*/
private array $regions;
/**
* @var array<int, PointExpiration>
*/
private array $point_expirations;
private int $default_certificate_id;
private AgencyEnrolmentType $enrolment_type;
/**
* @var IFieldSpecification[]
*/
private array $agency_accreditation_fields;
/**
* @var IFieldSpecification[]
*/
private array $agency_license_fields;
/**
* @var IFieldSpecification[]
*/
private array $agency_user_fields;
public function __construct(
?int $id = null,
string $organisation_code = '',
string $display_name = '',
string $website = '',
string $description = '',
?TimedFrontendNotice $frontend_notice = null,
array $point_types = [],
array $regions = [],
array $profession_codes = [],
array $point_expirations = [],
int $default_certificate_id = 0,
AgencyEnrolmentType $enrolment_type = AgencyEnrolmentType::manual,
array $agency_accreditation_fields = [],
array $agency_license_fields = [],
array $agency_user_fields = []
) {
tgbl_cred_assert_type( $point_types, 'int', 'Point types must be integers');
tgbl_cred_assert_type( $point_expirations, PointExpiration::class, 'Each point expiration must be an instance of PointExpiration' );
tgbl_cred_assert_type( $agency_accreditation_fields, IFieldSpecification::class, 'Each agency accreditation field must be an instance of IFieldSpecification' );
tgbl_cred_assert_type( $agency_license_fields, LicenseFieldSpecification::class, 'Each agency license field must be an instance of IFieldSpecification' );
tgbl_cred_assert_type( $agency_user_fields, IFieldSpecification::class, 'Each agency user field must be an instance of IFieldSpecification' );
I've added a few lines from the constructor to get a feeling of how we're enforcing the type rules. There are getters for all of these values; setters only on an as-needed basis.
An astute observer will notice later on that some of this code is already outdated - one will notice (in later sections, as of yet not written) that we've begun using specialised ___List
objects (IntList
, StringList
, FieldSpecificationList
, etc.), but here we're using generic arrays whose type adherence we check using tgbl_cred_assert_type
. The reader should feel encouraged by this - we don't need optimal soutions, we need good solutions whose trade-offs we understand. In this case, using arrays is easy, but we can forget the typecheck. Using a typed list is easier, but we need to create another class. No generics for us yet !
Let us then think about what we're looking at !
We call this large object in the software philosophy that inspired and is in active use in Tangible Cred (Domain-Driven Design) an Aggregate.
An Aggregate is a fancy name for a type of Entity (another fancy name here - an Entity is essentially anything with a lifecycle/identity based on a unique ID such as an autoincremented integer). An "Aggregate" is responsible for enforcing its own rules regarding internal consistency.